﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using BloggerAutoPosterService.Engine.Models;
using BloggerAutoPosterService.Engine.Utils;

namespace BloggerAutoPosterService.Engine.Core
{
    public class XmlDB
    {
        #region Fields (1)

        readonly IServiceConfig _serviceConfig;

        #endregion Fields

        #region Constructors (1)

        public XmlDB(IServiceConfig serviceConfig)
        {
            _serviceConfig = serviceConfig;
        }

        #endregion Constructors

        #region Methods (11)

        // Public Methods (7) 

        public void AddNewRecord(NewsRecord newsRecord)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            var query = findRecord(xmlDoc, newsRecord);
            if (query.Any()) return;

            var xElement = xmlDoc.Element("DailyEntries");
            if (xElement == null) return;

            xElement.Add(constructRecord(newsRecord));
            trySave(xmlDoc, newsRecord);
        }

        public void DeleteRecord(NewsRecord newsRecord)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            var query = findRecord(xmlDoc, newsRecord);
            if (query == null || !query.Any()) return;

            foreach (var xe in query)
            {
                xe.SetElementValue("IsDeleted", "True");
            }
            trySave(xmlDoc, newsRecord);
        }

        public void DeleteRecords(IEnumerable<NewsRecord> newsRecords)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            if (xmlDoc.Root == null) return;
            foreach (var xe in xmlDoc.Root.Elements("Entry")
                                           .Where(xe =>
                                            {
                                                var xElement = xe.Element("UrlHash");
                                                return newsRecords.Any(x => xElement != null && x.UrlHash == xElement.Value);
                                            }).ToList())
            {
                xe.SetElementValue("IsDeleted", "True");
            }
            trySave(xmlDoc, newsRecords);
        }

        public void EditRecords(IList<NewsRecord> newsRecords)
        {
            if (newsRecords == null) return;
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            if (xmlDoc.Root == null) return;
            foreach (var newsRecord in newsRecords.ToList())
            {
                var xe = xmlDoc.Root.Elements("Entry").Where(x =>
                                                                {
                                                                    var xElement = x.Element("UrlHash");
                                                                    return xElement != null && xElement.Value == newsRecord.UrlHash;
                                                                }).FirstOrDefault();
                if (xe == null) continue;
                xe.SetElementValue("Author", newsRecord.Author);
                xe.SetElementValue("Url", newsRecord.Link);
                xe.SetElementValue("UrlHash", newsRecord.UrlHash);
                xe.SetElementValue("Title", newsRecord.Title);
                xe.SetElementValue("AddDate", newsRecord.AddDate);
                xe.SetElementValue("Annotation", newsRecord.Annotation);
                xe.SetElementValue("Sent", newsRecord.Sent);
                xe.SetElementValue("IsDeleted", newsRecord.IsDeleted);
            }
            trySave(xmlDoc, newsRecords);
        }

        public IList<NewsRecord> ListNewsRecords(bool includeDeletedRecords)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            if (xmlDoc.Root == null) return new List<NewsRecord>();

            var query = xmlDoc.Root.Elements("Entry").Select(
                        x => new NewsRecord
                        {
                            Author = x.Element("Author").SafeXElementValue(),
                            Link = x.Element("Url").SafeXElementValue(),
                            UrlHash = x.Element("UrlHash").SafeXElementValue(),
                            Title = x.Element("Title").SafeXElementValue(),
                            AddDate = x.Element("AddDate").SafeXElementValue(),
                            Annotation = x.Element("Annotation").SafeXElementValue(),
                            Sent = x.Element("Sent").SafeXElementValue().ToLowerInvariant() == "true",
                            IsDeleted = x.Element("IsDeleted").SafeXElementValue().ToLowerInvariant() == "true"
                        });

            if (!includeDeletedRecords)
            {
                query = query.Where(x => !x.IsDeleted);
            }

            return query.ToList();
        }

        public void MarkAsSent(NewsRecord newsRecord)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            var query = findRecord(xmlDoc, newsRecord);
            if (query == null || !query.Any()) return;

            foreach (var xe in query.ToList())
            {
                xe.SetElementValue("Sent", "True");
            }
            trySave(xmlDoc, newsRecord);
        }

        public void MarkAsSent(IEnumerable<NewsRecord> newsRecords)
        {
            var xmlDoc = XDocument.Load(_serviceConfig.DBName);
            if (xmlDoc.Root == null) return;
            foreach (var xe in xmlDoc.Root.Elements("Entry").Where(xe =>
                                                                    {
                                                                        var xElement = xe.Element("UrlHash");
                                                                        return newsRecords.Any(x => xElement != null && x.UrlHash == xElement.Value);
                                                                    }))
            {
                xe.SetElementValue("Sent", "True");
            }
            trySave(xmlDoc, newsRecords);
        }
        // Private Methods (4) 

        private static XElement constructRecord(NewsRecord newsRecord)
        {
            return new XElement("Entry",
                                   new XElement("Author", newsRecord.Author),
                                   new XElement("Url", newsRecord.Link),
                                   new XElement("UrlHash", newsRecord.UrlHash),
                                   new XElement("Title", newsRecord.Title),
                                   new XElement("AddDate", newsRecord.AddDate),
                                   new XElement("Annotation", newsRecord.Annotation),
                                   new XElement("Sent", newsRecord.Sent),
                                   new XElement("IsDeleted", newsRecord.IsDeleted));
        }

        private static IList<XElement> findRecord(XDocument xmlDoc, NewsRecord newsRecord)
        {
            if (xmlDoc.Root == null) return new List<XElement>();
            return xmlDoc.Root.Elements("Entry").Where(e =>
                                                            {
                                                                var xElement = e.Element("UrlHash");
                                                                return xElement != null && xElement.Value == newsRecord.UrlHash;
                                                            }).ToList();
        }

        void trySave(XDocument xmlDoc, NewsRecord newsRecord)
        {
            try
            {
                if (!CheckPermission.HasWritePermission(_serviceConfig.DBName)) return;
                xmlDoc.Save(_serviceConfig.DBName);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("Root element is missing") || ex.Message.Contains("Could not find file"))
                {
                    //this is a new file, create a new record
                    var root = new XElement("DailyEntries");
                    var elm1 = constructRecord(newsRecord);
                    root.Add(elm1);
                    root.Save(_serviceConfig.DBName);
                }
                else
                {
                    throw;
                }
            }
        }

        void trySave(XDocument xmlDoc, IEnumerable<NewsRecord> newsRecords)
        {
            try
            {
                xmlDoc.Save(_serviceConfig.DBName);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("Root element is missing") || ex.Message.Contains("Could not find file"))
                {
                    //this is a new file, create a new record
                    var root = new XElement("DailyEntries");
                    foreach (var newsRecord in newsRecords)
                    {
                        var elm1 = constructRecord(newsRecord);
                        root.Add(elm1);
                    }
                    root.Save(_serviceConfig.DBName);
                }
                else
                {
                    throw;
                }
            }
        }

        #endregion Methods
    }
}
